#include "matrix.h"
#include "matrixThread.h"
#include <iostream>
using namespace std;

int Matrix::create(char * filename, int row) 
{
  QFile outfile(filename);
  if (! outfile.open(QIODevice::WriteOnly | QIODevice::Text))
    {
      cerr << "open " << filename << " failed" << endl;
      return -1;
    }
  QTextStream outstream(& outfile);
  for (int r = 0; r < row; r ++)
    {
      for (int c = 0; c < row; c ++)
	{
	  outstream << rand() % 100 << " ";
	}
      outstream << endl;
    }
  outfile.close();
  return 0;
}

int Matrix::allocateMemory(int n)
{
  numRow = n;
  elements = new int *[n];
  if (elements == NULL) 
    {
      cerr << "memory allocation fail"<< endl;
      return -1;
    }
  for (int r = 0; r <  n; r ++)
    {
      elements[r] = new int [n];

      if (elements[r] == NULL) 
	{
	  cerr << "memory allocation fail"<< endl;
	  return -1;
	}

      for(int c = 0; c < n; c++)
        {
          elements[r][c] = 0;
        }
    }
  return 0;
}

void Matrix::assignRow(int r, QString & oneLine)
{
  QStringList qsl = oneLine.split(" ");
  for (int c = 0; c < numRow; c ++)
    {
      elements[r][c] = qsl[c].toInt();
    }
}

Matrix::Matrix(int n)
{
  allocateMemory(n);
}

Matrix::Matrix(char * filename) 
{
  QFile infile(filename);
  if (! infile.open(QIODevice::ReadOnly | QIODevice::Text))
    {
      cerr << "open " << filename << " failed" << endl;
      return;
    }
  QTextStream instream(&infile);
  QString oneLine = instream.readLine();
  int n = oneLine.count(" ");
  if (n <= 0)
    {
      cerr << "wrong matrix size" << endl;
      return;
    }
  else
    {
      // cout << "matrix size: " << n << endl;
    }
  if (allocateMemory(n) == 0)
    {
      int r = 0;
      assignRow(r, oneLine);
      for (int r = 1; r < n; r ++)
	{
	  oneLine = instream.readLine();
	  assignRow(r, oneLine);
	}
    }
  infile.close();
}

void Matrix::copyElement(const Matrix & orig) 
{
  if (allocateMemory(orig.numRow) != 0)
    {
      return;
    }
  for (int r = 0; r < numRow; r ++)
    {
      for (int c = 0; c < numRow; c ++)
	{
	  elements[r][c] = orig.elements[r][c];
	}
    }
}

Matrix::Matrix(const Matrix & orig) 
{
  copyElement(orig);
}

Matrix & Matrix::operator = (const Matrix & orig)
{
  if (this != & orig)
    {
      // cannot use "delete this" because it will also release the
      // space for numRow
      for (int r = 0; r < numRow; r ++)
	{
	  delete [] elements[r];
	}
      delete [] elements;
      copyElement(orig);
    }
  return * this;
}

Matrix::~Matrix() {
  if (elements) 
    {
      for (int r = 0; r < numRow; r ++)
	{
	  delete [] elements[r];
	}
      delete [] elements;
    }
}

vector<MatrixThread> threads;

Matrix * Matrix::multiply(Matrix * B, int thread)
{
  MatrixThread threads[64];

  if (numRow != B -> numRow)
    {
      cerr << "different dimensions"<< endl;
      return 0;
    }

  Matrix * C = new Matrix(numRow);
  int n = numRow;
  int b = 32;

  // b = sqrt(L1 / 4)

  for (int i0 = 0; i0 < n; i0+=b)
    {
      for (int j0 = 0; j0 < n; j0+=b)
        {
          for (int k0 = 0; k0 < n; k0+=b)
            {
              for (int i = i0; i < min(i0 + b, n); i++)
                {
                  for (int j = j0; j < min(j0 + b, n); j++)
                    {
                      threads[0] = new MatrixThread(this, B, C, i, j, 0);
                      threads[1] = new MatrixThread(this, B, C, i, j, 1);
                      threads[2] = new MatrixThread(this, B, C, i, j, 2);
                      threads[3] = new MatrixThread(this, B, C, i, j, 3);
                      threads[4] = new MatrixThread(this, B, C, i, j, 4);
                      threads[5] = new MatrixThread(this, B, C, i, j, 5);
                      threads[6] = new MatrixThread(this, B, C, i, j, 6);
                      threads[7] = new MatrixThread(this, B, C, i, j, 7);
                      threads[8] = new MatrixThread(this, B, C, i, j, 8);
                      threads[9] = new MatrixThread(this, B, C, i, j, 9);
                      threads[10] = new MatrixThread(this, B, C, i, j, 10);
                      threads[11] = new MatrixThread(this, B, C, i, j, 11);
                      threads[12] = new MatrixThread(this, B, C, i, j, 12);
                      threads[13] = new MatrixThread(this, B, C, i, j, 13);
                      threads[14] = new MatrixThread(this, B, C, i, j, 14);
                      threads[15] = new MatrixThread(this, B, C, i, j, 15);
                      threads[16] = new MatrixThread(this, B, C, i, j, 16);
                      threads[17] = new MatrixThread(this, B, C, i, j, 17);
                      threads[18] = new MatrixThread(this, B, C, i, j, 18);
                      threads[19] = new MatrixThread(this, B, C, i, j, 19);
                      threads[20] = new MatrixThread(this, B, C, i, j, 20);
                      threads[21] = new MatrixThread(this, B, C, i, j, 21);
                      threads[22] = new MatrixThread(this, B, C, i, j, 22);
                      threads[23] = new MatrixThread(this, B, C, i, j, 23);
                      threads[24] = new MatrixThread(this, B, C, i, j, 24);
                      threads[25] = new MatrixThread(this, B, C, i, j, 25);
                      threads[26] = new MatrixThread(this, B, C, i, j, 26);
                      threads[27] = new MatrixThread(this, B, C, i, j, 27);
                      threads[28] = new MatrixThread(this, B, C, i, j, 28);
                      threads[29] = new MatrixThread(this, B, C, i, j, 29);
                      threads[30] = new MatrixThread(this, B, C, i, j, 30);
                      threads[31] = new MatrixThread(this, B, C, i, j, 31);

                      threads[0].start();
                      threads[1].start();
                      threads[2].start();
                      threads[3].start();
                      threads[4].start();
                      threads[5].start();
                      threads[6].start();
                      threads[7].start();
                      threads[8].start();
                      threads[9].start();
                      threads[10].start();
                      threads[11].start();
                      threads[12].start();
                      threads[13].start();
                      threads[14].start();
                      threads[15].start();
                      threads[16].start();
                      threads[17].start();
                      threads[18].start();
                      threads[19].start();
                      threads[20].start();
                      threads[21].start();
                      threads[22].start();
                      threads[23].start();
                      threads[24].start();
                      threads[25].start();
                      threads[26].start();
                      threads[27].start();
                      threads[28].start();
                      threads[29].start();
                      threads[30].start();
                      threads[31].start();

                      threads[0].wait();
                      threads[1].wait();
                      threads[2].wait();
                      threads[3].wait();
                      threads[4].wait();
                      threads[5].wait();
                      threads[6].wait();
                      threads[7].wait();
                      threads[8].wait();
                      threads[9].wait();
                      threads[10].wait();
                      threads[11].wait();
                      threads[12].wait();
                      threads[13].wait();
                      threads[14].wait();
                      threads[15].wait();
                      threads[16].wait();
                      threads[17].wait();
                      threads[18].wait();
                      threads[19].wait();
                      threads[20].wait();
                      threads[21].wait();
                      threads[22].wait();
                      threads[23].wait();
                      threads[24].wait();
                      threads[25].wait();
                      threads[26].wait();
                      threads[27].wait();
                      threads[28].wait();
                      threads[29].wait();
                      threads[30].wait();
                      threads[31].wait();
                      
                      for (int k = 0; k < 32; k++)
                        {
                          C -> elements[i][j] = threads[k].getResult();
                        }
                    }
                }
            }
        }
    }

  return C;
}

void Matrix::print(char * filename)
{
  QFile outfile(filename);
  if (! outfile.open(QIODevice::WriteOnly | QIODevice::Text))
    {
      cerr << "open " << filename << " failed" << endl;
      return;
    }
  QTextStream outstream(& outfile);
  for (int r = 0; r < numRow; r ++)
    {
      for (int c = 0; c < numRow; c ++)
	{
	  outstream << elements[r][c] << " ";
	}
      outstream << endl;
    }
  outfile.close();
}

int Matrix::min(int num1, int num2)
{
  int minimum;

  if(num1 < num2)
    {
      minimum = num1;
    }
  else
    {
      minimum = num2;
    }

  return minimum;
}

int Matrix::get(int element1, int element2)
{
  return elements[element1][element2];
}
